/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.exp;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import edu.uoc.sd.fr14.Configuration;
import edu.uoc.sd.fr14.ExperimentConfiguration;
import edu.uoc.sd.fr14.Node;
import edu.uoc.sd.fr14.comp.CustomerComponent;
import edu.uoc.sd.fr14.comp.ServerComponent;
import edu.uoc.sd.fr14.mesg.TargetId;
import edu.uoc.sd.fr14.model.CustomerId;
import edu.uoc.sd.fr14.model.Logger;
import edu.uoc.sd.fr14.model.ServerId;
import edu.uoc.sd.fr14.model.ServerState;

/**
 *
 * @author drodenas
 */
public class ExperimentPhaseX extends Experiment {

    private static final Logger log = Logger.get("Experiment");
    private int delay;
    private int times;
    private final Node node;
    private final TargetId me;
    private final Configuration config;
    private final ExperimentConfiguration experimentConfig;
    private final ExperimentComponent experimentComponent;

    @SuppressWarnings("rawtypes")
	public ExperimentPhaseX(Set mes, Configuration config, ResultCollector resultCollector) {
        super(mes, config, resultCollector);
        
        this.me = (TargetId) mes.iterator().next();
        this.config = config;
        this.experimentConfig = config.experiment();

        saveString("state", "");
        
        node = new Node();
        node.buildSockets(config);
        
        experimentComponent = new ExperimentComponent(me, this, config.experiment().getExperimentTick());
        node.addComponent(experimentComponent);

        saveString("state", "INITIALIZED");        
    }

    @Override
    public void tick() {
        if (delay > 0) {
            delay = delay - 1;
            return;
        }
        times = times + 1;
        
        String state = getString("state");
        switch (state) {
            case "":
                break;
                
            case "INITIALIZED":
                stateBarrier("INITIALIZED", "START_SERVERS");
                break;
                
            case "START_SERVERS":
            	if (times == 1) startServers1();
                delay = experimentConfig.getCustomerDelay();
                stateBarrier("START_SERVERS","START_CUSTOMERS");
                break;
                
            case "START_CUSTOMERS":
                node.buildCustomers(config);
                stateBarrier("START_CUSTOMERS","RUN");
                break;
                
            case "RUN":
                delay = experimentConfig.getExecutionTime();
                stateBarrier("RUN","STOP_CUSTOMERS");
                break;
                
            case "STOP_CUSTOMERS":
                node.stopCustomers();
                stateBarrier("STOP_CUSTOMERS","STOP_CLOCK");
                break;                

            case "STOP_CLOCK":
            	if (times == 1) {  }
                node.stopClock();
                delay = experimentConfig.getShutdownDelay();
                stateBarrier("STOP_CLOCK","CLEAN_UP");
                break;
                
            case "CLEAN_UP":
            	if (times == 1) cleanUp1();
            	else            cleanUpN();
            	if (times > 5) {
            		stateBarrier("CLEAN_UP","COLLECT_RESULTS");
            	}
                break;
                
            case "COLLECT_RESULTS":
            	if (times == 1) { collectResults1(); }
                stateBarrier("COLLECT_RESULTS","STOP_SERVERS");
                break;
                
            case "STOP_SERVERS":
                node.stopServers();
                stateBarrier("STOP_SERVERS","EXIT");
                break;
                
            case "EXIT":
            	if (times == 1) { exit1(); }
                break;
                
            default:
                throw new IllegalStateException("Illegal state: '"+state+"'");
        }
    }
    
    protected boolean isStateRun() {
    	return getString("state").equals("RUN");
    }
    
    protected void startServers1() {    	
        node.buildClock(config.servers(), config.clockConfiguration());
        node.buildServers(config);
    }
    
    protected void cleanUp1() {    	
    }
    
    protected void cleanUpN() {    	
    }
    
    protected void collectResults1() {
    	collectServerResults();
    	collectCustomerResults();
    }
    
    private void collectServerResults() {
    	Set<ServerId> mes = config.me();
    	TreeMap<ServerId,ServerState> results = new TreeMap<>();
    	for (ServerId server : mes) {
			ServerState state = node.getServer(server).getServerState();
			results.put(server, state);
		}

    	saveObject("serverResults", results);
    }
    
    private void collectCustomerResults() {
    	if (!config.operator()) { return; }
    	
    	Set<CustomerId> cus = config.customers();
    	TreeMap<CustomerId,CustomerComponent.Stats> results = new TreeMap<>();
    	for (CustomerId customer : cus) {
    		CustomerComponent.Stats state = node.getCustomer(customer).getStats();
			results.put(customer, state);
		}

    	saveObject("customerResults", results);
    }
    
    protected void exit1() {
    	//super.dumpState();
    	
        node.stopSockets();
        experimentComponent.stop();
        Node.shutdown();
        //System.exit(1);

        // parse results
        Map<ServerId,ServerState> servers = new TreeMap<>();
        Map<CustomerId,CustomerComponent.Stats> customers = new TreeMap<>();
        for (Map<String, Object> value : getStates().values()) {
        	@SuppressWarnings("unchecked") Map<ServerId,ServerState> ss = (Map<ServerId,ServerState>) value.get("serverResults");
        	@SuppressWarnings("unchecked") Map<CustomerId,CustomerComponent.Stats> cs = (Map<CustomerId,CustomerComponent.Stats>) value.get("customerResults");
        	if (ss != null) servers.putAll(ss);
        	if (cs != null) customers.putAll(cs);
		}

        StringBuilder result = new StringBuilder();
        boolean success = validateResult(servers, customers, result);
        result.append(""
        		+ "==================================\n"
				+ "=== RESULT: " + success + "\n"
        		+ "==================================\n"
        		+ "");
        
        collectResult(success, result.toString());
    }
    
    protected boolean validateResult(Map<ServerId,ServerState> servers, Map<CustomerId,CustomerComponent.Stats> customers, StringBuilder result) {
        result.append(""
        		+ "==================================\n"
				+ "===------------------------------\n"
				+ "=== SERVERS\n"
				+ servers
				+ "\n"
				+ "===------------------------------\n"
				+ "=== CUSTOMERS\n"
				+ customers
				+ "\n"
				+ "===------------------------------\n"
				+ "");
    	return true;
    }
    
    protected ServerComponent getMyServer() {
    	ServerComponent server;
    	if (me instanceof ServerId) {
    		server = node.getServer((ServerId) me);
    	} else {
    		server = null;
    	}
    	return server;
    }
    
    private void stateBarrier(String expected, String next) {
        if (othersEqual("state", expected,next)) {
            log.debug(me, "stateBarrier() state: '"+expected+"'->'"+next+"'");
            times = 0;
            saveString("state", next);
        }
    }

    protected void emulateManualSetLeader() {
        Set<ServerId> serverIds = config.servers();
        Set<ServerId> mes = config.me();
        
        ServerId leader = serverIds.iterator().next();
        for (ServerId server : mes) {
			ServerState state = node.getServer(server).getServerState();
			state.getRole().setLeader(leader);
		}
    }
    
	protected void debug(String message) {
		log.debug(me, message);
	}

}
